import 'dart:math';
import 'dart:typed_data';

import 'package:lib_bitcoin_base/src/bitcoin/address/core.dart';
import 'package:lib_bitcoin_base/src/models/network.dart';
import 'package:lib_bitcoin_base/src/provider/mempol_models.dart';
import 'package:blockchain_utils/hd_wallet/hd_wallet.dart';
import 'package:convert/convert.dart';

import '../../bitcoin_base.dart';
import '../example/spending_with_scripts/spending_builders.dart';
import '../provider/api_provider.dart';
import '../provider/transaction_builder.dart';
import '../provider/utxo_details.dart';

enum BitcoinAddressType {
  legacy, //p2pkh
  nativeSegWit, //p2wpkh
  nestedSegWit, //p2wpkhInp2sh
  tapRoot //p2tr
}

class BuildBitcoinTransaction {
  BtcTransaction transaction;
  BigInt fee;

  BuildBitcoinTransaction(this.transaction, this.fee);
}

class BitcoinTransactionDetails {
  int? block;
  int? fee;
  bool confirmed;

  BitcoinTransactionDetails(this.block, this.fee, this.confirmed);
}

class SimpleBitcoin {
  SimpleBitcoin._();

  static final SimpleBitcoin _instance = SimpleBitcoin._();

  factory SimpleBitcoin() {
    return _instance;
  }

  static const int decimals = 8;

  String convertPrivateKeyHex2Wif(String privateHex) {
    ECPrivate ecPrivate =
        ECPrivate.fromBytes(Uint8List.fromList(hex.decode(privateHex)));
    return ecPrivate.toWif(compressed: true, networkType: NetworkInfo.BITCOIN);
  }

  String convertWif2PrivateKeyHex(String privateWif) {
    ECPrivate ecPrivate = ECPrivate.fromWif(privateWif);
    return ecPrivate.toHex();
  }

  String generateBitcoinPrivateKeyHexByMnemonic(String mnemonic) {
    final masterWallet = BIP32HWallet.fromMnemonic(mnemonic);
    final pk = BIP32HWallet.drivePath(masterWallet, "m/44'/0'/0'/0/0");
    return ECPrivate.fromBytes(pk.privateKey).toHex();
  }

  Map<BitcoinAddressType, String> generateBitcoinAddress(
      String privateKeyHex, bool mainNet) {
    Map<BitcoinAddressType, String> res = {};

    NetworkInfo network = _getNetwork(mainNet);
    ECPrivate ecPrivate =
        ECPrivate.fromBytes(Uint8List.fromList(hex.decode(privateKeyHex)));

    //p2pkh 以1开头 Legacy 普通
    String legacy = ecPrivate.getPublic().toAddress().toAddress(network);
    //segWit 以bc1q开头 Native SegWit 隔离见证(原生)
    String nativeSegWit =
        ecPrivate.getPublic().toSegwitAddress().toAddress(network);
    //p2sh 以3开头 Nested SegWit 隔离见证(兼容)
    String nestedSegWit =
        ecPrivate.getPublic().toP2wpkhInP2sh().toAddress(network);
    //taproot 以bc1开头 Taproot Taproot
    String taproot =
        ecPrivate.getPublic().toTaprootAddress().toAddress(network);

    res[BitcoinAddressType.legacy] = legacy;
    res[BitcoinAddressType.nativeSegWit] = nativeSegWit;
    res[BitcoinAddressType.nestedSegWit] = nestedSegWit;
    res[BitcoinAddressType.tapRoot] = taproot;
    return res;
  }

  bool validAddress(String address, bool mainNet) {
    try {
      return null != getBitcoinAddressType(address, mainNet);
    } catch (e) {
      return false;
    }
  }

  bool validAddressIgnoreNet(String address) {
    try {
      return null != getBitcoinAddressType(address, true) ||
          null != getBitcoinAddressType(address, false);
    } catch (e) {
      return false;
    }
  }

  // BitcoinAddressType? getBitcoinAddressType(String address, bool mainNet) {
  //   NetworkInfo network = _getNetwork(mainNet);
  //   if (isValidAddress(address, AddressType.p2pkh, network: network)) {
  //     return BitcoinAddressType.legacy;
  //   } else if (isValidAddress(address, AddressType.p2wpkh, network: network)) {
  //     return BitcoinAddressType.nativeSegWit;
  //   } else if (isValidAddress(address, AddressType.p2wpkhInP2sh,
  //       network: network)) {
  //     return BitcoinAddressType.nestedSegWit;
  //   } else if (isValidAddress(address, AddressType.p2tr, network: network)) {
  //     return BitcoinAddressType.tapRoot;
  //   } else {
  //     return null;
  //   }
  // }
  BitcoinAddressType? getBitcoinAddressType(String address, bool mainNet) {
    if (isBitcoinLegacyAddress(address, mainNet)) {
      return BitcoinAddressType.legacy;
    } else if (isBitcoinNativeSegWitAddress(address, mainNet)) {
      return BitcoinAddressType.nativeSegWit;
    } else if (isBitcoinNestedSegWitAddress(address, mainNet)) {
      return BitcoinAddressType.nestedSegWit;
    } else if (isBitcoinTapRootAddress(address, mainNet)) {
      return BitcoinAddressType.tapRoot;
    } else {
      return null;
    }
  }

  bool isBitcoinLegacyAddress(String address, bool mainNet) {
    String reg = "^[${mainNet ? '1' : 'nm'}][a-km-zA-HJ-NP-Z1-9]{25,39}\$";
    RegExp regExp = RegExp(reg);
    return regExp.hasMatch(address);
  }

  bool isBitcoinNestedSegWitAddress(String address, bool mainNet) {
    String reg = "^[${mainNet ? '3' : '2'}][a-km-zA-HJ-NP-Z1-9]{25,39}\$";
    RegExp regExp = RegExp(reg);
    return regExp.hasMatch(address);
  }

  bool isBitcoinNativeSegWitAddress(String address, bool mainNet) {
    String reg = "^(${mainNet ? 'bc1' : 'tb1'})[a-zA-HJ-NP-Z0-9]{25,39}\$";
    RegExp regExp = RegExp(reg);
    return regExp.hasMatch(address);
  }

  bool isBitcoinTapRootAddress(String address, bool mainNet) {
    String reg =
        "^((${mainNet ? 'bc' : 'tb'})(0([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59})|1[ac-hj-np-z02-9]{8,89}))\$";
    RegExp regExp = RegExp(reg);
    return regExp.hasMatch(address);
  }

  BitcoinAddress convertBitcoinAddress(String address, bool mainNet) {
    BitcoinAddressType? bitcoinAddressType =
        getBitcoinAddressType(address, mainNet);
    if (null == bitcoinAddressType) {
      throw 'invalid address or network';
    }

    switch (bitcoinAddressType) {
      case BitcoinAddressType.legacy:
        return P2pkhAddress(address: address);
      case BitcoinAddressType.nativeSegWit:
        return P2wpkhAddress(address: address);
      case BitcoinAddressType.nestedSegWit:
        return P2wpkhInP2shAddress(address: address);
      case BitcoinAddressType.tapRoot:
        return P2trAddress(address: address);
    }
  }

  NetworkInfo _getNetwork(bool mainNet) {
    return mainNet ? NetworkInfo.BITCOIN : NetworkInfo.TESTNET;
  }

  Future<BigInt> getBitcoinAddressBalance(String address, bool mainNet) async {
    NetworkInfo network = _getNetwork(mainNet);
    final api = ApiProvider.fromMempl(network);
    return api.getAccountBalance(address);
  }

  Future<BuildBitcoinTransaction> buildBtcTransaction(
      {required String privateKeyHex,
      required BitcoinAddressType sendAddressType,
      required String receiveAddress,
      required BigInt sendAmount,
      required bool mainNet}) async {
    if (!validAddress(receiveAddress, mainNet)) {
      throw 'invalid address';
    }

    NetworkInfo network = _getNetwork(mainNet);
    final api = ApiProvider.fromMempl(network);

    ECPrivate ecPrivate =
        ECPrivate.fromBytes(Uint8List.fromList(hex.decode(privateKeyHex)));

    final pubKey = ecPrivate.getPublic();
    late BitcoinAddress sender;
    switch (sendAddressType) {
      case BitcoinAddressType.legacy:
        sender = pubKey.toAddress();
        break;
      case BitcoinAddressType.nativeSegWit:
        sender = pubKey.toSegwitAddress();
        break;
      case BitcoinAddressType.nestedSegWit:
        sender = pubKey.toP2wpkhInP2sh();
        break;
      case BitcoinAddressType.tapRoot:
        sender = pubKey.toTaprootAddress();
        break;
    }

    final utxo = await api.getAccountUtxo(
        UtxoOwnerDetails(address: sender, publicKey: pubKey.toHex()));
    final sumOfUtxo = utxo.sumOfUtxosValue();
    if (sumOfUtxo == BigInt.zero) {
      throw Exception(
          "account does not have any unspent transaction or mybe no confirmed");
    }

    final feeRate = await api.getNetworkFeeRate();

    final receiver = convertBitcoinAddress(receiveAddress, mainNet);
    final changeAddress = sender;

    final List<BitcoinAddress> outputsAdress = [receiver, changeAddress];
    final transactionSize = BitcoinTransactionBuilder.estimateTransactionSize(
        utxos: utxo, outputs: outputsAdress, network: network);
    var estimateFee =
        feeRate.getEstimate(transactionSize, feeRate: feeRate.medium);
    if (!mainNet) {
      if (estimateFee < BigInt.from(266)) {
        estimateFee = BigInt.from(266);
      }
    }

    final canSpend = sumOfUtxo - estimateFee;

    print(
        'sender:${sender.toAddress(network)},总量:$sumOfUtxo,gas:$estimateFee,最多可以发送:$canSpend');

    // if (canSpend < sendAmount) {
    //   throw 'balance not enough';
    // }

    final List<BitcoinOutputDetails> outPutWithValue = [];
    outPutWithValue.add(
        BitcoinOutputDetails(address: outputsAdress[0], value: sendAmount));
    outPutWithValue.add(BitcoinOutputDetails(
        address: outputsAdress[1], value: canSpend - sendAmount));

    outPutWithValue.forEach((element) {
      print(
          'address:${element.address.toAddress(network)},value:${element.value}');
    });

    BtcTransaction transaction;

    switch (sendAddressType) {
      case BitcoinAddressType.legacy:
        transaction = buildP2pkhTransaction(
          receiver: outPutWithValue,
          sign: (p0, publicKey, sigHash) {
            return ecPrivate.signInput(p0, sigHash: sigHash);
          },
          utxo: utxo,
        );
        break;
      case BitcoinAddressType.nativeSegWit:
        transaction = buildP2wpkTransaction(
          receiver: outPutWithValue,
          sign: (p0, publicKey, sigHash) {
            return ecPrivate.signInput(p0, sigHash: sigHash);
          },
          utxo: utxo,
        );
        break;
      case BitcoinAddressType.nestedSegWit:
        final transactionBuilder = BitcoinTransactionBuilder(
          utxos: utxo,
          outPuts: outPutWithValue,
          fee: estimateFee,
          network: network,
          enableRBF: true,
        );
        transaction =
            transactionBuilder.buildTransaction((trDigest, utxo, publicKey) {
          if (utxo.utxo.isP2tr()) {
            return ecPrivate.signTapRoot(trDigest);
          } else {
            return ecPrivate.signInput(trDigest);
          }
        });
        break;
      case BitcoinAddressType.tapRoot:
        transaction = buildP2trTransaction(
          receiver: outPutWithValue,
          sign: (p0, publicKey, sigHash) {
            return ecPrivate.signTapRoot(p0, sighash: sigHash, tweak: true);
          },
          utxo: utxo,
        );
        break;
    }

    return BuildBitcoinTransaction(transaction, estimateFee);
  }

  Future<String> sendBitcoinTransaction(
      BtcTransaction transaction, bool mainNet) async {
    NetworkInfo network = _getNetwork(mainNet);
    final api = ApiProvider.fromMempl(network);
    final ser = transaction.serialize();
    final id = transaction.txId();
    final result = await api.sendRawTransaction(ser);
    return result;
  }

  Future<BitcoinTransactionDetails> getBitcoinTransactionInfo(
      String txId, bool mainNet) async {
    NetworkInfo network = _getNetwork(mainNet);
    final api = ApiProvider.fromMempl(network);
    MempoolTransaction result = await api.getTransaction(txId);
    final block = result.status.blockHeight;
    final fee = result.fee;
    final confirmed = result.status.confirmed;
    return BitcoinTransactionDetails(block, fee, confirmed);
  }

  Future<List<Map<String, dynamic>>> getBitcoinTransactionList(
      String wallet, bool mainNet) async {
    List<Map<String, dynamic>> result = [];

    NetworkInfo network = _getNetwork(mainNet);
    final api = ApiProvider.fromMempl(network);
    List<MempoolTransaction> res = await api.getAccountTransactions(wallet);

    for (MempoolTransaction element in res) {
      int totalInValue = 0;
      String? inAddress;
      for (var element in element.vin) {
        String address = element.prevOut.scriptPubKeyAddress;
        int value = element.prevOut.value;
        if (address == wallet) {
          totalInValue += value;
        } else {
          inAddress = address;
        }
      }
      int totalOutValue = 0;
      int firstWalletOutValue = 0;
      String? outAddress;
      for (var element in element.vout) {
        String? address = element.scriptPubKeyAddress;
        int? value = element.value;
        if (address == wallet) {
          totalOutValue += value;
          if (0 == firstWalletOutValue) {
            firstWalletOutValue = value;
          }
        } else if (null != address) {
          outAddress = address;
        }
      }

      bool? send;
      int amount;
      if (null == inAddress && null == outAddress) {
        //自己转给自己
        amount = firstWalletOutValue;
      } else {
        send = totalInValue > totalOutValue;
        amount = (totalInValue - totalOutValue).abs();
        if (send) {
          amount -= element.fee;
        }
      }

      Map<String, dynamic> item = {};
      item['txId'] = element.txID;
      item['pending'] = !element.status.confirmed;
      item['height'] = null == element.status.blockHeight
          ? ''
          : element.status.blockHeight.toString();
      item['transactionTime'] = element.status.blockTime.toString();
      if (null == send) {
        item['from'] = wallet;
        item['to'] = wallet;
      } else {
        if (send) {
          item['from'] = wallet;
          item['to'] = outAddress;
        } else {
          item['from'] = inAddress;
          item['to'] = wallet;
        }
      }
      item['amount'] = (amount / pow(10, decimals)).toString();
      item['txFee'] = (element.fee / pow(10, decimals)).toString();

      result.add(item);
    }

    return result;
  }
}
